package caizw.hibernate.base.dao.impl;

import caizw.hibernate.base.dao.Base;
import caizw.hibernate.base.dao.PageInfo;
import org.hibernate.Criteria;
import org.hibernate.query.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * @author caizw
 * T ---> 泛型类
 * PK ---> 泛型类的主键的java数据类型
 */
@Repository
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public abstract class BaseImpl<T, PK extends Serializable> implements Base<T, PK> {

    private static final String ORDER_DESC = "desc";
    private static final String ORDER_ASC = "asc";

    @Resource
    private SessionFactory sessionFactory;

    @Override
    public Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public void save(T entity) {
        getSession().save(entity);
    }

    @Override
    public void delete(T entity) {
        getSession().delete(entity);
    }

    @Override
    public void update(T entity) {
        entity = getUpdateEntity(entity);
        getSession().update(entity);
    }

    @Override
    public void saveOrUpdate(T entity) {
        entity = getUpdateEntity(entity);
        this.getSession().saveOrUpdate(entity);
    }

    @Override
    public T queryById(PK id) {
        if (id == null) {
            return null;
        } else {
            return (T) getSession().get(getClazzGenricType(), id);
        }
    }

    @Override
    public List<T> queryAll() {
        return getSession().createQuery("FROM " + getClazzGenricType().getSimpleName()).list();
    }

    @Override
    public List<T> queryListByHql(String queryString, Object... params) {
        Query queryObject = getSession().createQuery(queryString);
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                queryObject.setParameter(i, params[i]);
            }
        }
        return queryObject.list();
    }

    /**
     * 根据HQL语句查询实体
     *
     * @param hql 待查询的HQL语句
     * @return
     */
    @Override
    public List<T> queryByHql(String hql) {
        return getSession()
                .createQuery(hql)
                .list();
    }

    /**
     * 单表通过 in 查询 只需要传入字段名，以及 in 集合
     *
     * @param filedName 字段名
     * @param values    集合
     * @return
     */
    @Override
    public List<T> queryByFiledIn(String filedName, List<? extends Object> values) {
        Class<T> clazz = getClazzGenricType();
        String hql = "from " + clazz.getSimpleName() + " WHERE " + filedName + " in (:values)";
        Query query = getSession().createQuery(hql);
        query.setParameterList("values", values);
        List<T> list = query.list();
        getSession().flush();
        if (list != null && list.size() != 0) {
            return list;
        } else {
            return new ArrayList<>();
        }
    }

    /**
     * 通过 criterion 查询
     *
     * @param criterion
     * @return
     */
    @Override
    public List<T> queryByCriterion(List<Criterion> criterion) {
        Criteria criteria = getSession().createCriteria(this.getClazzGenricType());
        for (Criterion criterion1 : criterion) {
            criteria.add(criterion1);
        }
        return criteria.list();
    }

    /**
     * @param hql      需要查询的hql语句
     * @param pageRow  查询第pageNo页的记录
     * @param pageSize 每页需要显示的记录数
     * @return 当前页的所有记录
     */
    @Override
    public PageInfo<T> findByPage(String hql, int pageRow, int pageSize) {
        Query query = getSession().createQuery(hql);
        List<T> list = query.setFirstResult((pageRow - 1) * pageSize)
                .setMaxResults(pageSize)
                .list();
        long count = findCount(this.getClazzGenricType());
        PageInfo<T> pageInfo = new PageInfo(pageRow, pageSize, getCountPage(count, pageSize), count, list);
        return pageInfo;
    }

    /**
     * 使用hql 语句进行分页查询操作
     *
     * @param hql      需要查询的HQL语句
     * @param pageRow  查询第pageRow页的记录
     * @param pageSize 每页需要显示的记录数
     * @param params   如果hql带占位符参数，params用于传入占位符参数
     * @return 当前页的所有记录
     */
    @Override
    public PageInfo<T> findByPage(String hql, int pageRow, int pageSize, Object... params) {
        Query query = getSession().createQuery(hql);
        for (int i = 0, len = params.length; i < len; i++) {
            query.setParameter(i + "", params[i]);
        }
        List<T> list = query.setFirstResult((pageRow - 1) + pageSize)
                .setMaxResults(pageSize)
                .list();
        long count = findCount(this.getClazzGenricType());
        PageInfo<T> pageInfo = new PageInfo(pageRow, pageSize, getCountPage(count, pageSize), count, list);
        return pageInfo;
    }

    /**
     * @param pageRow         当前第几页
     * @param pageSize        每页需要显示的记录数
     * @param pram            map 查询条件
     * @param orderColumnName 排序字段
     * @param order           按什么进行排序
     * @return
     */
    @Override
    public PageInfo<T> getPageInfo(Integer pageRow, Integer pageSize, Map<String, Object> pram, String orderColumnName, String order) {
        Class<T> clazz = getClazzGenricType();
        Criteria criteria = getSession().createCriteria(getClazzGenricType());
        if (pram != null) {
            Set keyset = pram.keySet();
            for (Object key : keyset) {
                if (key == null || pram.get(key) == null) {
                    continue;
                }
                if (pram.get(key).getClass() == String.class) {
                    criteria.add(Restrictions.like(key.toString(), pram.get(key)));
                } else {
                    criteria.add(Restrictions.eq(key.toString(), pram.get(key)));
                }
            }
        }
        if (orderColumnName != null && BaseImpl.ORDER_DESC.equalsIgnoreCase(order)) {
            criteria.addOrder(Order.desc(orderColumnName));
        }
        if (orderColumnName != null && BaseImpl.ORDER_ASC.equalsIgnoreCase(order)) {
            criteria.addOrder(Order.asc(orderColumnName));
        }
        if (pageRow != null) {
            criteria.setFirstResult(pageRow);
        } else {
            criteria.setFirstResult(0);
        }
        if (pageSize != null) {
            criteria.setMaxResults(pageSize);
        }
        List list = criteria.list();
        long count = findCount(clazz);
        PageInfo<T> pageInfo = new PageInfo(pageRow, pageSize, getCountPage(count, pageSize), count, list);
        return pageInfo;
    }

    /**
     * 如果id存在，则根据id查询出对象，返回查询出的此对象，否则直接返回存入对象
     * @param entity
     * @return
     */
    private T getUpdateEntity(T entity) {
        PK pkValueByName = getPkValueByName(entity);
        if (!StringUtils.isEmpty(pkValueByName)) {
            T t = getSession().get(getClazzGenricType(), pkValueByName);
            try {
                reflectClass1ToClass2(entity, t);
                return t;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return entity;
    }

    private long findCount(Class<T> clazz) {
        String hql = "select count(*) from " + clazz.getSimpleName();
        List list = queryByHql(hql);
        if (list != null && list.size() > 0) {
            return (long) list.get(0);
        }
        return 0;
    }

    /**
     * 通过反射,获得定义Class时声明的泛型参数的类型
     */
    private Class<T> getClazzGenricType() {
        Class<T> entityClass = null;
        Type t = getClass().getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            Type[] p = ((ParameterizedType) t).getActualTypeArguments();
            entityClass = (Class<T>) p[0];
        }
        return entityClass;
    }

    /**
     * 统计分多少页
     *
     * @param total
     * @param pageSize
     * @return
     */
    private long getCountPage(long total, long pageSize) {
        long countPage = 1;
        if (total % pageSize == 0) {
            countPage = total / pageSize;
        } else {
            countPage = (total / pageSize) + 1;
        }
        return countPage;
    }

    /**
     * 获取主键名称
     *
     * @return
     */
    private String getPkName() {
        ClassMetadata meta = this.sessionFactory.getClassMetadata(getClazzGenricType());
        String pkName = meta.getIdentifierPropertyName();
        return pkName;
    }

    /**
     * 根据属性名获取属性值
     */
    private PK getPkValueByName(T t) {
        String pkName = getPkName();
        try {
            String firstLetter = pkName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + pkName.substring(1);
            Method method = t.getClass().getMethod(getter, new Class[]{});
            PK value = (PK) method.invoke(t, new Object[]{});
            return value;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 通过反射请传入的对象的属性值赋值给查询处理的对象
     *
     * @param class1
     * @param class2
     * @throws Exception
     */
    private void reflectClass1ToClass2(Object class1, Object class2) throws Exception {
        /**
         * 获取实体类的所有属性，返回Field数组
         */
        Field[] field1 = getFieldAll(class1);
        Field[] field2 = getFieldAll(class2);
        for (int i = 0; i < field1.length; i++) {
            /**
             * 获取属性的名字
             */
            String name = field1[i].getName();
            if ("serialVersionUID".equals(name)) {
                continue;
            }
            /**
             * 将属性的首字符大写，方便构造get，set方法
             */
            name = name.substring(0, 1).toUpperCase() + name.substring(1);
            Method m1 = class1.getClass().getMethod("get" + name);
            /**
             * 调用getter方法获取属性值
             */
            Object value = m1.invoke(class1);
            if (value != null) {
                Field f = field2[i];
                /**
                 * 设置些属性是可以访问的
                 */
                f.setAccessible(true);
                f.set(class2, value);
            }
        }
    }

    /**
     * 获取Field，包括父类的Field
     *
     * @param clazz
     * @return
     */
    private Field[] getFieldAll(Object clazz) {
        List<Class<?>> list = getSuperClass(clazz.getClass());
        list.add(clazz.getClass());
        List<Field> fieldList = new ArrayList<>();
        for (Class<?> clazz1 : list) {
            Field[] declaredFields = clazz1.getDeclaredFields();
            for (Field field : declaredFields) {
                fieldList.add(field);
            }
        }
        Field[] fieldArray = new Field[fieldList.size()];
        Field[] fields = null;
        if (list.size() > 0) {
            fields = fieldList.toArray(fieldArray);
        }
        return fields;
    }

    /**
     * 获取父类
     *
     * @param calzz
     * @return
     */
    private List<Class<?>> getSuperClass(Class<?> calzz) {
        List<Class<?>> listSuperClass = new ArrayList<Class<?>>();
        Class<?> superclass = calzz.getSuperclass();
        while (superclass != null) {
            if (superclass.getName().equals("java.lang.Object")) {
                break;
            }
            listSuperClass.add(superclass);
            superclass = superclass.getSuperclass();
        }
        return listSuperClass;
    }
}
